// UnrealDebugger.cpp : main project file.

#include "stdafx.h"
#include "DebuggerInterface.h"

#include <gcroot.h>

using namespace System;
using namespace System::IO;
using namespace System::Threading;
using namespace System::Windows::Forms;
using namespace System::Collections::Generic;
using namespace UnrealDebugger;

//------------------------------------------------------------------

#ifdef _DUMP_LOG 
#define LOG(x)								\
	if (g_Log)\
	{\
		String^ text = String::Format("[{0}] {1}", DateTime::Now.ToString("MM/dd/yyyy h:mm tt"), x);\
		g_Log->WriteLine(text);\
		g_Log->Flush();\
	}
#else
#define LOG(x)
#endif

//------------------------------------------------------------------

ref class Lock 
{
	Object^ m_pObject;

public:
	Lock( Object ^ pObject ) : m_pObject(nullptr)
	{
		if (pObject != nullptr)
		{
			m_pObject = pObject;
			//Monitor::Enter( m_pObject );
		}
	}

	~Lock() 
	{
		if (m_pObject)
		{
			//Monitor::Exit( m_pObject );
		}
	}
};

//------------------------------------------------------------------

typedef void (*CallbackPointer)(const char*);

gcroot<Object^> g_CriticalRegion;
gcroot<UnrealDebuggerIDE^> g_Debugger;
gcroot<AutoResetEvent^> g_Event;

#ifdef _DUMP_LOG
gcroot<StreamWriter^> g_Log;
#endif

CallbackPointer g_Callback;

int g_WatchId[3];
gcroot<UnlockListAction::WatchList^> g_WatchBuffer[3];

//------------------------------------------------------------------

void CommandHandler(String^ command)
{
	char* szCommand = (char*)(System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(command)).ToPointer();
	
	LOG(String::Format("Command: {0}", command));
	if (g_Callback) { g_Callback(szCommand); }

	System::Runtime::InteropServices::Marshal::FreeHGlobal(IntPtr((void*)szCommand));
}

//------------------------------------------------------------------

void WriteBooleanValueToMemoryHandler(bool newValue, String^ address)
{
	try
	{
		String^ delimStr = ":";
		array<Char>^ delimiter = { ':' };
		array<String^>^ tokens = address->Split(delimiter, StringSplitOptions::RemoveEmptyEntries);

#ifdef _WIN64
		IntPtr^ pointer = gcnew IntPtr(Convert::ToInt64(tokens[0],16));
#else
		IntPtr^ pointer = gcnew IntPtr(Convert::ToInt32(tokens[0],16));
#endif

		int offset = Convert::ToInt32(tokens[1]);
		int byte = offset / 8;
		int bit = offset % 8;

		unsigned char* pValue = (unsigned char*) pointer->ToPointer();
		pValue += byte;
		*pValue = newValue ? *pValue | (1 << bit) : *pValue & (~(1 << bit));
	}
	catch (Exception^ e)
	{
		ExceptionMessageBox^ msgBox = gcnew ExceptionMessageBox(e);
		msgBox->ShowDialog();
	}
}

//------------------------------------------------------------------

void WriteIntegerValueToMemoryHandler(int newValue, String^ address)
{
	try
	{

#ifdef _WIN64
		IntPtr^ pointer = gcnew IntPtr(Convert::ToInt64(address,16));
#else
		IntPtr^ pointer = gcnew IntPtr(Convert::ToInt32(address,16));
#endif

		int* pValue = (int*) pointer->ToPointer();
		*pValue = newValue;
	}
	catch (Exception^ e)
	{
		ExceptionMessageBox^ msgBox = gcnew ExceptionMessageBox(e);
		msgBox->ShowDialog();
	}
}

//------------------------------------------------------------------

void WriteFloatValueToMemoryHandler(float newValue, String^ address)
{
	try
	{

#ifdef _WIN64
		IntPtr^ pointer = gcnew IntPtr(Convert::ToInt64(address,16));
#else
		IntPtr^ pointer = gcnew IntPtr(Convert::ToInt32(address,16));
#endif

		float* pValue = (float*) pointer->ToPointer();
		*pValue = newValue;
	}
	catch (Exception^ e)
	{
		ExceptionMessageBox^ msgBox = gcnew ExceptionMessageBox(e);
		msgBox->ShowDialog();
	}
}

//------------------------------------------------------------------

void UnhandledExceptionHandler(Object^ sender, UnhandledExceptionEventArgs^ e)
{
	try
	{
		ExceptionMessageBox^ msgBox = gcnew ExceptionMessageBox((Exception^) e->ExceptionObject);
		msgBox->ShowDialog();
	}
	finally
	{
	    throw (Exception^) e->ExceptionObject;
	}
}

//------------------------------------------------------------------

void ThreadExceptionHandler(Object^ sender, ThreadExceptionEventArgs^ e)
{
	try
	{
		ExceptionMessageBox^ msgBox = gcnew ExceptionMessageBox(e->Exception);
		msgBox->ShowDialog();
	}
	finally
	{
	    throw e->Exception;
	}
}

//------------------------------------------------------------------

[STAThread]
void WorkerThreadFunction()
{
	AppDomain::CurrentDomain->UnhandledException += gcnew UnhandledExceptionEventHandler(&UnhandledExceptionHandler);
	Application::ThreadException += gcnew ThreadExceptionEventHandler(&ThreadExceptionHandler);

	Application::EnableVisualStyles();
	Application::SetCompatibleTextRenderingDefault(false); 

	{
		Lock lock(g_CriticalRegion);
		g_Debugger = UnrealDebuggerIDE::Instance;
		g_Debugger->OnCommand += gcnew UnrealDebuggerIDE::CommandDelegate(&CommandHandler);
		g_Debugger->OnWriteBooleanValueToMemory += gcnew UnrealDebuggerIDE::WriteBooleanValueToMemoryDelegate(&WriteBooleanValueToMemoryHandler);
		g_Debugger->OnWriteIntegerValueToMemory += gcnew UnrealDebuggerIDE::WriteIntegerValueToMemoryDelegate(&WriteIntegerValueToMemoryHandler);
		g_Debugger->OnWriteFloatValueToMemory += gcnew UnrealDebuggerIDE::WriteFloatValueToMemoryDelegate(&WriteFloatValueToMemoryHandler);
	}

	g_Event->Set();

	Application::Run(g_Debugger);

	{
		Lock lock(g_CriticalRegion);

#ifdef _DUMP_LOG

		if (g_Log) { g_Log->Close(); }
		g_Log = nullptr;

#endif

		g_Callback = NULL;
		g_Debugger = nullptr;
		g_Event = nullptr;
	}
}

//------------------------------------------------------------------

extern "C"
{

//------------------------------------------------------------------

void SetCallback(void* CallbackFunc)
{
	Lock lock(g_CriticalRegion);

	if (!g_Debugger)
	{

#ifdef _DUMP_LOG

		g_Log = gcnew StreamWriter("debugger.log");
		g_Log->WriteLine("Debugger started [{0}]", DateTime::Now.ToString("MM/dd/yyyy h:mm tt"));
		g_Log->WriteLine("==================================================================");

#endif

		g_Event = gcnew AutoResetEvent(false);
		g_CriticalRegion = gcnew Object();

		Thread ^ thread = gcnew Thread(gcnew ThreadStart(WorkerThreadFunction));
		thread->Name = "Unreal Debugger";
		thread->IsBackground = true;
		thread->SetApartmentState(ApartmentState::STA);
		thread->Start();
		g_Event->WaitOne();
	}

	LOG(String::Format("SetCallback"));
	g_Callback = (CallbackPointer) CallbackFunc;
}

//------------------------------------------------------------------

void ShowDllForm()
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG("ShowDllForm");
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::ShowDllFormAction());
	}
}

//------------------------------------------------------------------

void BuildHierarchy()
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("BuildHierarchy"));
	}
}

//------------------------------------------------------------------

void ClearHierarchy()
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("ClearHierarchy"));
	}
}

//------------------------------------------------------------------

void AddClassToHierarchy(const char* ClassName)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("AddClassToHierarchy: {0}", gcnew String(ClassName)));
	}
}

//------------------------------------------------------------------

void ClearWatch(int WatchType)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("ClearWatch: {0}", WatchType));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::ClearAWatchAction(WatchType));
		g_WatchId[WatchType] = 0;
	}
}

//------------------------------------------------------------------

void ClearAWatch(int WatchType)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("ClearAWatch: {0}", WatchType));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::ClearAWatchAction(WatchType));

		g_WatchId[WatchType] = 0;
		g_WatchBuffer[WatchType] = gcnew UnlockListAction::WatchList();
	}
}

//------------------------------------------------------------------

int AddAWatch(int WatchType, int ParentIndex, const char* VarName, const char* VarValue)
{
	int nResult = 0;
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("AddAWatch: {0},{1}: {2} = {3}", WatchType, ParentIndex, gcnew String(VarName), gcnew String(VarValue)));

		nResult = ++g_WatchId[WatchType];

		if (g_WatchBuffer[WatchType])
		{
			UnlockListAction::Watch^ watch = gcnew UnlockListAction::Watch();
			watch->Index = nResult;
			watch->ParentIndex = ParentIndex;
			watch->VarName = gcnew String(VarName);
			watch->VarValue = gcnew String(VarValue);

			g_WatchBuffer[WatchType]->Add(watch);
		}
	}
	
	return nResult;
}

//------------------------------------------------------------------

void LockList(int WatchList)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("LockList: {0}", WatchList));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::LockListAction(WatchList));
	}
}

//------------------------------------------------------------------

void UnlockList(int WatchList)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("UnlockList: {0}", WatchList));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::UnlockListAction(WatchList, g_WatchBuffer[WatchList]));
		g_WatchBuffer[WatchList] = nullptr;
	}
}

//------------------------------------------------------------------

void AddBreakpoint(const char* ClassName, int LineNo)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("AddBreakpoint: {0}:{1}", gcnew String(ClassName), LineNo));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::ConnectBreakpointAction(gcnew String(ClassName), LineNo));
	}
}

//------------------------------------------------------------------

void RemoveBreakpoint(const char* ClassName, int LineNo)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("RemoveBreakpoint: {0}:{1}", gcnew String(ClassName), LineNo));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::DisconnectBreakpointAction(gcnew String(ClassName), LineNo));
	}
}

//------------------------------------------------------------------

void EditorLoadClass(const char* ClassName)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("EditorLoadClass: {0}", gcnew String(ClassName)));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::EditorLoadClassAction(gcnew String(ClassName)));
	}
}

//------------------------------------------------------------------

void EditorGotoLine(int LineNo, int bHighlight)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("EditorGotoLine: {0}({1})", LineNo, bHighlight));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::EditorGotoLineAction(LineNo, bHighlight));
	}
}

//------------------------------------------------------------------

void AddLineToLog(const char* Text)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("AddLineToLog: {0}", gcnew String(Text)));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::AddLineToLogAction(gcnew String(Text)));

		if (String::Compare(gcnew String("Log: Detaching UnrealScript Debugger (currently detached)"), gcnew String(Text), true) == 0)
		{

#ifdef _DUMP_LOG

			g_Log->Close();
			g_Log = nullptr;

#endif
			g_Callback = NULL;
			g_Debugger->OnCommand -= gcnew UnrealDebuggerIDE::CommandDelegate(&CommandHandler);
			g_Debugger->Invoke(gcnew MethodInvoker(g_Debugger, &Form::Close));
			g_Debugger = nullptr;
		}
	}
}

//------------------------------------------------------------------

void CallStackClear()
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("CallStackClear"));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::CallStackClearAction());
	}
}

//------------------------------------------------------------------

void CallStackAdd(const char* CallStackEntry)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("CallStackAdd: {0}", gcnew String(CallStackEntry)));
		g_Debugger->ExecuteAction(gcnew UnrealDebugger::CallStackEntryAction(gcnew String(CallStackEntry)));
	}
}

//------------------------------------------------------------------

void SetCurrentObjectName(const char* ObjectName)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("SetCurrentObjectName: {0}", gcnew String(ObjectName)));
	}
}

//------------------------------------------------------------------

void DebugWindowState(int StateCode)
{
	Lock lock(g_CriticalRegion);

	if (g_Debugger && !g_Debugger->IsDisposed)
	{
		LOG(String::Format("DebugWindowState: {0}", StateCode));
	}
}

//------------------------------------------------------------------

#pragma managed(push, off)

BOOL APIENTRY DllMain(
    HMODULE hModule,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpReserved )  // reserved
{
    // Perform actions based on the reason for calling.
    switch( fdwReason ) 
    { 
        case DLL_PROCESS_ATTACH:
         // Initialize once for each new process.
         // Return FALSE to fail DLL load.

			g_Callback = NULL;
			memset(g_WatchId, 0, sizeof(g_WatchId));

            break;

        case DLL_THREAD_ATTACH:
         // Do thread-specific initialization.
            break;

        case DLL_THREAD_DETACH:
         // Do thread-specific cleanup.
            break;

        case DLL_PROCESS_DETACH:
         // Perform any necessary cleanup.
            break;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

#pragma managed(pop)

//------------------------------------------------------------------

}

